// Microsoft Public License (Ms-PL)
//
// This license governs use of the accompanying software. If you use the software, you accept this license.
// If you do not accept the license, do not use the software.
//
// 1. Definitions
//
// The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here 
// as under U.S. copyright law.
//
// A "contribution" is the original software, or any additions or changes to the software.
//
// A "contributor" is any person that distributes its contribution under this license.
//
// "Licensed patents" are a contributor's patent claims that read directly on its contribution.
//
// 2. Grant of Rights
//
// (A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations 
// in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to 
// reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution 
// or any derivative works that you create.
//
// (B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in 
// section 3, each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed 
// patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution 
// in the software or derivative works of the contribution in the software.
//
// 3. Conditions and Limitations
//
// (A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or 
// trademarks.
//
// (B) If you bring a patent claim against any contributor over patents that you claim are infringed by the 
// software, your patent license from such contributor to the software ends automatically.
//
// (C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and 
// attribution notices that are present in the software.
//
// (D) If you distribute any portion of the software in source code form, you may do so only under this license 
// by including a complete copy of this license with your distribution. If you distribute any portion of the 
// software in compiled or object code form, you may only do so under a license that complies with this license.
//
// (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express 
// warranties, guarantees or conditions. You may have additional consumer rights under your local laws which 
// this license cannot change. To the extent permitted under your local laws, the contributors exclude the 
// implied warranties of merchantability, fitness for a particular purpose and non-infringement.

using System;
using System.Collections;
using System.Data;
using System.Text;
using System.Web.Services;
using System.Web.Services.Protocols;
using StoreFront.BusinessRule;
using StoreFront.BusinessRule.Management;
using StoreFront.BusinessRule.Orders;
using StoreFront.DataAccess;
using StoreFront.SystemBase;
using StoreFrontSecurity;

namespace StructuredSolutions.ShippingAgent
{
	[WebService(Namespace = "http://www.codeplex.com/ShippingAgent/2008-12-01",
		 Description = "Shipping Agent for StoreFront 6.8 by Structured Solutions.")]
	public class ShippingAgentImpl : ShippingAgentSoap
	{
		#region WebMethod Implementations

		/// <summary>
		/// Return orders placed since lastOrderNumber. If lastOrderNumber is null or an empty
		/// string, then return all orders from today.
		/// </summary>
		/// <param name="lastOrderNumber">The last order number in the previous results. May be
		/// null or an empty string if this is the first time this method is called.</param>
		/// <param name="maxCount">The maximum number of orders to return.</param>
		/// <returns>Up to maxCount OrderResults, sorted by order number; or an empty list or null 
		/// if there are no new orders.</returns>
		/// <remarks>
		/// <para>If maxCount orders are returned, the Shipper client will call this method again with the 
		/// same ranges and with lastOrderNumber set to the last order number in the previous result. In this
		/// case, use lastOrderNumber to calculate an appropriate new startOrderNumber (for example,
		/// startOrderNumber = lastOrderNumber + 1).</para>
		/// <para>Return a <see cref="SoapException"/> if the parameters are invalid.</para>
		/// </remarks>
		[WebMethod(Description = "Retrieve orders placed since lastOrderNumber.")]
		public override OrderResult[] GetNewOrders(string lastOrderNumber, int maxCount)
		{
			try
			{
				return GetNewOrdersFromStore(lastOrderNumber, maxCount);
			}
			catch (ApplicationException ex)
			{
				throw new DetailedSoapException(ex.Message, SoapException.ServerFaultCode, ex);
			}
			catch (Exception ex)
			{
				throw new DetailedSoapException("Cannot retrieve new orders.", SoapException.ServerFaultCode, ex);
			}
		}

		/// <summary>
		/// Return one order.
		/// </summary>
		/// <param name="orderId">The ID of the order.</param>
		/// <returns>The OrderResult.</returns>
		/// <exception cref="SoapException">If the orderID is not valid (such as blank or null).</exception>
		[WebMethod(Description = "Retrieve one order.")]
		public override OrderResult GetOrder(string orderId)
		{
			try
			{
				if (orderId == null || orderId == string.Empty)
					throw new ApplicationException("Invalid order ID.");
				return GetOrderFromStore(orderId);
			}
			catch (ApplicationException ex)
			{
				OrderResult result = new OrderResult();
				result.Success = false;
				result.Message = ex.Message;
				return result;
			}
			catch (Exception ex)
			{
				OrderResult result = new OrderResult();
				result.Success = false;
				result.Message = ex.ToString();
				return result;
			}
		}

		/// <summary>
		/// Return the current inventory for all items in one order.
		/// </summary>
		/// <param name="orderId">The ID of the order.</param>
		/// <returns>A list of item Inventory objects or null if inventory is not tracked.</returns>
		/// <exception cref="SoapException">If the orderID is not valid (such as blank or null).</exception>
		[WebMethod(Description = "Retrieve the current inventory for all items in one order.")]
		public override InventoryResult GetOrderInventory(string orderId)
		{
			try
			{
				if (orderId == null || orderId == string.Empty)
					throw new ApplicationException("Invalid order ID.");
				return GetOrderInventoryFromStore(orderId);
			}
			catch (ApplicationException ex)
			{
				InventoryResult result = new InventoryResult();
				result.Success = false;
				result.Message = ex.Message;
				return result;
			}
			catch (Exception ex)
			{
				InventoryResult result = new InventoryResult();
				result.Success = false;
				result.Message = ex.ToString();
				return result;
			}
		}

		/// <summary>
		/// Return all the orders that fall on and within the specified ranges.
		/// </summary>
		/// <param name="startOrderNumber">The starting order number; or null or an empty string to start at the first order number in the store.</param>
		/// <param name="endOrderNumber">The ending order number; or null or an empty string to end with the last order number in the store.</param>
		/// <param name="startOrderDate">The starting order date; or a date less than or equal to 1/1/1753 to start with the first order date in the store.</param>
		/// <param name="endOrderDate">The ending order date; or a date equal to or greater than 12/31/9999 to end with the last order date in the store.</param>
		/// <param name="lastOrderNumber">The last order number from the previous result; or null or an empty string to ignore this parameter.</param>
		/// <param name="maxCount">The maximum number of orders to return.</param>
		/// <returns>Up to maxCount Order's, sorted by order number; or an empty list or null 
		/// if there are no orders that fall on and within the specified ranges.</returns>
		/// <remarks>If maxCount orders are returned, the Shipper client will call this method again with the 
		/// same ranges and with lastOrderNumber set to the last order number in the previous result. In this
		/// case, use lastOrderNumber to calculate an appropriate new startOrderNumber (for example,
		/// startOrderNumber = lastOrderNumber + 1).
		/// </remarks>
		[WebMethod(Description = "Return the list of orders that fall on and within the specified ranges.")]
		public override OrderResult[] GetOrdersByRange(string startOrderNumber, string endOrderNumber, DateTime startOrderDate, bool startOrderDateSpecified, DateTime endOrderDate, bool endOrderDateSpecified, string lastOrderNumber, int maxCount)
		{
			try
			{
				if (!startOrderDateSpecified) startOrderDate = DateTime.MinValue;
				if (!endOrderDateSpecified) endOrderDate = DateTime.MinValue;
				return GetOrdersByRangeFromStore(startOrderNumber, endOrderNumber, startOrderDate, endOrderDate, lastOrderNumber, maxCount);
			}
			catch (ApplicationException ex)
			{
				throw new DetailedSoapException(ex.Message, SoapException.ServerFaultCode, ex);
			}
			catch (Exception ex)
			{
				throw new DetailedSoapException("Cannot retrieve orders by range.", SoapException.ServerFaultCode, ex);
			}
		}

		/// <summary>
		/// Return one specific package.
		/// </summary>
		/// <param name="orderId">The ID of the order.</param>
		/// <param name="packageId">The ID of the package.</param>
		/// <returns>A PackageResult.</returns>
		/// <exception cref="SoapException">If the orderId or packageId is not valid (such as blank or null).</exception>
		[WebMethod(Description = "Retrieve one package.")]
		public override PackageResult GetPackage(string orderId, string packageId)
		{
			try
			{
				if (orderId == null || orderId == string.Empty)
					throw new ApplicationException("Invalid order ID.");
				if (packageId == null || packageId == string.Empty)
					throw new ApplicationException("Invalid package ID.");
				return GetPackageFromStore(orderId, packageId);
			}
			catch (ApplicationException ex)
			{
				PackageResult result = new PackageResult();
				result.Success = false;
				result.Message = ex.Message;
				return result;
			}
			catch (Exception ex)
			{
				PackageResult result = new PackageResult();
				result.Success = false;
				result.Message = ex.ToString();
				return result;
			}
		}

		/// <summary>
		/// Returns the complete list of tracking providers supported by the store. The
		/// Shipper client may assign any tracking provider to a package, regardless of
		/// how the shipping cost was initially calculated.
		/// </summary>
		/// <returns>The list of TrackingProvider's.</returns>
		[WebMethod(Description = "Retrieve the list of package tracking providers.")]
		public override TrackingProvider[] GetTrackingProviders()
		{
			try
			{
				return GetTrackingProvidersFromStore();
			}
			catch (ApplicationException ex)
			{
				throw new DetailedSoapException(ex.Message, SoapException.ServerFaultCode, ex);
			}
			catch (Exception ex)
			{
				throw new DetailedSoapException("Cannot retrieve tracking providers.", SoapException.ServerFaultCode, ex);
			}
		}

		/// <summary>
		/// Record package information.
		/// </summary>
		/// <param name="update">The package information to record.</param>
		/// <returns>If the update is successful, the UpdateResult contains a fresh
		/// copy of the Order that contains the package. If the update is not
		/// successful, the UpdateResult contains the reason why the update failed.</returns>
		/// <remarks>The Shipper client may not include all Update elements. null
		/// elements should be ignored. If the ShipDate is included, then the package
		/// should be marked shipped.</remarks>
		[WebMethod(Description = "Record package information.")]
		public override OrderResult UpdatePackage(Update update)
		{
			try
			{
				if (update == null)
					return null;
				if (update.OrderId == null || update.OrderId == string.Empty)
					throw new ApplicationException("Invalid order ID.");
				if (update.PackageId == null || update.PackageId == string.Empty)
					throw new ApplicationException("Invalid package ID.");
				return UpdatePackageInStore(update);
			}
			catch (ApplicationException ex)
			{
				OrderResult result = new OrderResult();
				result.Success = false;
				result.Message = ex.Message;
				return result;
			}
			catch (Exception ex)
			{
				OrderResult result = new OrderResult();
				result.Success = false;
				result.Message = ex.ToString();
				return result;
			}
		}

		#endregion

		#region Platform Specific Methods

		protected OrderResult[] GetNewOrdersFromStore(string lastOrderNumber, int maxCount)
		{
			ArrayList orders = new ArrayList();
			CSearchDate search = new CSearchDate(CSearchDate.DateRange.Custom, DateTime.MinValue.ToString(), DateTime.MaxValue.ToString());

			long sfLastOrderNumber = 0;

			// If lastOrderNumber is missing, then return orders from today.
			if (lastOrderNumber == null || lastOrderNumber == string.Empty)
				search.FromDate = DateTime.Today;
			else
			{
				try
				{
					sfLastOrderNumber = long.Parse(lastOrderNumber);
				}
				catch
				{
					throw new ApplicationException("Invalid last order number.");
				}

				sfSearchContainer searchContainer = new sfSearchContainer();
				searchContainer.OrderNumber = sfLastOrderNumber;
				using (CManagement management = new CManagement(searchContainer))
				{
					ArrayList summaries = management.OrderSummary;
					if (summaries.Count == 0)
						throw new ApplicationException("Invalid last order number.");

					DateTime sfOrderDate = ShippingAgentHelper.ConvertToDateTime(((COrderManagmentSummary)summaries[0]).OrderDate);
					if (sfOrderDate > DateTime.MinValue)
						search.FromDate = sfOrderDate;
				}
			}

			using (CManagement management = new CManagement(search, ShipStatus.Pending, PaymentStatus.Not_Criteria))
			{
				foreach (COrderManagmentSummary sfOrderSummary in management.OrderSummary)
				{
					if (sfOrderSummary.OrderNumber <= sfLastOrderNumber)
						continue;

					orders.Add(BuildOrderResult(sfOrderSummary));
					if (orders.Count == maxCount) break;
				}
			}

			// Sort the shipments by OrderNumber so that the most recent OrderNumber is last
			orders.Sort(new OrderSorter());

			return (OrderResult[]) orders.ToArray(typeof(OrderResult));
		}

		/// <summary>
		/// Return one Order from the store.
		/// </summary>
		/// <param name="orderId">The OrderNumber of the StoreFront COrder object.</param>
		/// <returns>The Order corresponding to the StoreFront COrder.</returns>
		protected OrderResult GetOrderFromStore(string orderId)
		{
			long sfOrderNumber;
			try
			{
				sfOrderNumber = long.Parse(orderId);
			}
			catch
			{
				throw new ApplicationException("Invalid order ID.");
			}

			return BuildOrderResult(sfOrderNumber);
		}

		protected InventoryResult GetOrderInventoryFromStore(string orderId)
		{
			long sfOrderNumber;
			try
			{
				sfOrderNumber = long.Parse(orderId);
			}
			catch
			{
				throw new ApplicationException("Invalid order ID.");
			}

			using (CManagement management = new CManagement(sfOrderNumber))
			{
				ArrayList summaries = management.OrderSummary;
				if (summaries.Count == 0)
					throw new ApplicationException("Invalid order ID.");

				return BuildInventoryResult((COrderManagmentSummary)summaries[0]);
			}
		}

		protected OrderResult[] GetOrdersByRangeFromStore(string startOrderNumber, string endOrderNumber, DateTime startOrderDate,
			DateTime endOrderDate, string lastOrderNumber, int maxCount)
		{
			ArrayList orders = new ArrayList();

			CSearchDate search = new CSearchDate(CSearchDate.DateRange.Custom, DateTime.MinValue.ToString(), DateTime.MaxValue.ToString());
			if (startOrderDate > DateTime.MinValue) search.FromDate = startOrderDate;
			if (endOrderDate > DateTime.MinValue) search.ToDate = endOrderDate;

			// StoreFront only supports order date ranges, so convert order number range to dates to 
			// reduce the number of orders in the result set
			long sfStartOrderNumber = 0;
			long sfEndOrderNumber = 0;
			long sfLastOrderNumber = 0;
			if (startOrderNumber != null && startOrderNumber != string.Empty)
			{
				try
				{
					sfStartOrderNumber = long.Parse(startOrderNumber);
				}
				catch
				{
					throw new ApplicationException("Invalid start order number.");
				}

				sfSearchContainer searchContainer = new sfSearchContainer();
				searchContainer.OrderNumber = sfStartOrderNumber;
				using (CManagement management = new CManagement(searchContainer))
				{
					ArrayList summaries = management.OrderSummary;
					if (summaries.Count == 0)
						throw new ApplicationException("Invalid start order number.");

					DateTime sfOrderDate = ShippingAgentHelper.ConvertToDateTime(((COrderManagmentSummary)summaries[0]).OrderDate);
					if (sfOrderDate > DateTime.MinValue && sfOrderDate > search.FromDate)
						search.FromDate = sfOrderDate;
				}
			}
			if (endOrderNumber != null && endOrderNumber != string.Empty)
			{
				try
				{
					sfEndOrderNumber = long.Parse(endOrderNumber);
				}
				catch
				{
					throw new ApplicationException("Invalid end order number.");
				}

				sfSearchContainer searchContainer = new sfSearchContainer();
				searchContainer.OrderNumber = sfEndOrderNumber;
				using (CManagement management = new CManagement(searchContainer))
				{
					ArrayList summaries = management.OrderSummary;
					if (summaries.Count == 0)
						throw new ApplicationException("Invalid end order number.");

					DateTime sfOrderDate = ShippingAgentHelper.ConvertToDateTime(((COrderManagmentSummary)summaries[0]).OrderDate);
					if (sfOrderDate > DateTime.MinValue && sfOrderDate < search.ToDate)
						search.ToDate = sfOrderDate;
				}
			}
			if (lastOrderNumber != null && lastOrderNumber != string.Empty)
			{
				try
				{
					sfLastOrderNumber = long.Parse(lastOrderNumber);
				}
				catch
				{
					throw new ApplicationException("Invalid last order number.");
				}

				sfSearchContainer searchContainer = new sfSearchContainer();
				searchContainer.OrderNumber = sfLastOrderNumber;
				using (CManagement management = new CManagement(searchContainer))
				{
					ArrayList summaries = management.OrderSummary;
					if (summaries.Count == 0)
						throw new ApplicationException("Invalid last order number.");

					DateTime sfOrderDate = ShippingAgentHelper.ConvertToDateTime(((COrderManagmentSummary)summaries[0]).OrderDate);
					if (sfOrderDate > DateTime.MinValue && sfOrderDate > search.FromDate)
						search.FromDate = sfOrderDate;
				}
			}

			using (CManagement management = new CManagement(search, ShipStatus.Not_Criteria, PaymentStatus.Not_Criteria))
			{
				foreach (COrderManagmentSummary sfOrderSummary in management.OrderSummary)
				{
					if ((startOrderNumber != null && startOrderNumber != string.Empty) && sfOrderSummary.OrderNumber < sfStartOrderNumber)
						continue;
					if ((lastOrderNumber != null && lastOrderNumber != string.Empty) && sfOrderSummary.OrderNumber <= sfLastOrderNumber)
						continue;
					if ((endOrderNumber != null && endOrderNumber != string.Empty) && sfOrderSummary.OrderNumber > sfEndOrderNumber)
						break;

					orders.Add(BuildOrderResult(sfOrderSummary));
					if (orders.Count == maxCount) break;
				}
			}

			// Sort the shipments by OrderNumber so that the highest OrderNumber is last
			orders.Sort(new OrderSorter());

			return (OrderResult[]) orders.ToArray(typeof(OrderResult));
		}

		/// <summary>
		/// Return one Shipment from the store.
		/// </summary>
		/// <param name="orderId">The OrderNumber of the StoreFront COrder object.</param>
		/// <param name="packageId">The ID of the corresponding OrderAddress object and a flag
		/// to indicate the order ("a") or backorder shipment ("b").</param>
		/// <returns>The Shipment.</returns>
		/// <remarks>StoreFront stores shipments in objects called OrderAddress. Each
		/// OrderAddress can contain 1 or 2 shipments: the order shipment and the
		/// backorder shipment.</remarks>
		protected PackageResult GetPackageFromStore(string orderId, string packageId)
		{
			long sfOrderNumber;
			try
			{
				sfOrderNumber = long.Parse(orderId);
			}
			catch
			{
				throw new ApplicationException("Invalid order ID.");
			}

			bool sfBackorder;
			if (packageId.ToLower().StartsWith("a"))
				sfBackorder = false;
			else if (packageId.ToLower().StartsWith("b"))
				sfBackorder = true;
			else
				throw new ApplicationException("Invalid package ID.");
			long sfOrderAddressId;

			try
			{
				sfOrderAddressId = long.Parse(packageId.Substring(1));
			}
			catch
			{
				throw new ApplicationException("Invalid package ID.");
			}

			using (COrder sfOrder = new COrder(sfOrderNumber))
			{
				foreach (COrderAddress sfOrderAddress in sfOrder.OrderAddresses)
				{
					if (sfOrderAddress.Address.ID == sfOrderAddressId)
						return BuildPackageResult(sfOrder, sfOrderAddress, sfBackorder);
				}
			}

			throw new ApplicationException("Invalid order or shipment ID.");
		}

		protected TrackingProvider[] GetTrackingProvidersFromStore()
		{
			ArrayList trackingProviders = new ArrayList();

			using (CShippingManagement management = new CShippingManagement())
			using (DataSet carriers = management.getCarriers())
			{
				foreach (DataRow row in carriers.Tables[0].Rows)
				{
					string name = ShippingAgentHelper.ConvertToString(row["Name"]);
					if (name == null || name == string.Empty) continue;

					string code = ShippingAgentHelper.ConvertToString(row["Code"]);
					if (code == null || code == string.Empty || string.Compare(code, "none", true) == 0) continue;

					TrackingProvider provider = new TrackingProvider();
					provider.Name = name;
					provider.TrackingProviderId = code;
					trackingProviders.Add(provider);
				}
			}

			return (TrackingProvider[]) trackingProviders.ToArray(typeof(TrackingProvider));
		}

		protected OrderResult UpdatePackageInStore(Update update)
		{
			long sfOrderNumber;
			try
			{
				sfOrderNumber = long.Parse(update.OrderId);
			}
			catch
			{
				throw new ApplicationException("Invalid order ID.");
			}

			bool sfBackorder;
			if (update.PackageId.ToLower().StartsWith("a"))
				sfBackorder = false;
			else if (update.PackageId.ToLower().StartsWith("b"))
				sfBackorder = true;
			else
				throw new ApplicationException("Invalid package ID.");
			long sfOrderAddressId;

			try
			{
				sfOrderAddressId = long.Parse(update.PackageId.Substring(1));
			}
			catch
			{
				throw new ApplicationException("Invalid package ID.");
			}

			using (COrder sfOrder = new COrder(sfOrderNumber))
			{
				foreach (COrderAddress sfOrderAddress in sfOrder.OrderAddresses)
				{
					if (sfOrderAddress.Address.ID == sfOrderAddressId)
						return BuildUpdateResult(update, sfOrder, sfOrderAddress, sfBackorder);
				}
			}
			throw new ApplicationException("Invalid order and shipment ID.");
		}

		#endregion

		private const string COD = "COD";
		private const string Discounts = "Discounts";
		private const string GiftCertificates = "Gift Certificates";
		private const string Giftwraps = "Gift Wraps";
		private const string Handling = "Handling";
		private const string Shipping = "Shipping";
		private const string Taxes = "Taxes";

		private static bool AddressesAreEqual(COrderAddress sfOrderAddress, ShippingAddress shippingAddress)
		{
			if (!ShippingAgentHelper.StringsAreEqual(sfOrderAddress.Address.City, shippingAddress.City, false)) return false;
			if (!ShippingAgentHelper.StringsAreEqual(sfOrderAddress.Address.Company, shippingAddress.Company, false)) return false;
			if (!ShippingAgentHelper.StringsAreEqual(sfOrderAddress.Address.Country, shippingAddress.Country, true)) return false;
			if (!ShippingAgentHelper.StringsAreEqual(sfOrderAddress.Address.Fax, shippingAddress.Fax, false)) return false;
			if (!ShippingAgentHelper.StringsAreEqual(sfOrderAddress.Address.FirstName, shippingAddress.FirstName, false)) return false;
			if (!ShippingAgentHelper.StringsAreEqual(sfOrderAddress.Address.LastName, shippingAddress.LastName, false)) return false;
			if (!ShippingAgentHelper.StringsAreEqual(sfOrderAddress.Address.Address1, shippingAddress.Line1, false)) return false;
			if (!ShippingAgentHelper.StringsAreEqual(sfOrderAddress.Address.Address2, shippingAddress.Line2, false)) return false;
			if (!ShippingAgentHelper.StringsAreEqual(sfOrderAddress.Address.Phone, shippingAddress.Phone, false)) return false;
			if (!ShippingAgentHelper.StringsAreEqual(sfOrderAddress.Address.Zip, shippingAddress.PostalCode, false)) return false;
			if (!ShippingAgentHelper.StringsAreEqual(sfOrderAddress.Address.State, shippingAddress.Region, true)) return false;
			if (sfOrderAddress.Residential != shippingAddress.Residential) return false;
			return true;
		}

		private static Address BuildBillingAddress(COrder sfOrder)
		{
			Address address = new Address();
			address.AddressId = sfOrder.BillAddress.ID.ToString();
			address.City = ShippingAgentHelper.StringOrNull(sfOrder.BillAddress.City);
			address.Company = ShippingAgentHelper.StringOrNull(sfOrder.BillAddress.Company);
			address.Country = ShippingAgentHelper.StringOrNull(sfOrder.BillAddress.Country);
			address.Email = ShippingAgentHelper.StringOrNull(sfOrder.BillAddress.EMail);
			address.Fax = ShippingAgentHelper.StringOrNull(sfOrder.BillAddress.Fax);
			address.FirstName = ShippingAgentHelper.StringOrNull(sfOrder.BillAddress.FirstName);
			address.LastName = ShippingAgentHelper.StringOrNull(sfOrder.BillAddress.LastName);
			address.Line1 = ShippingAgentHelper.StringOrNull(sfOrder.BillAddress.Address1);
			address.Line2 = ShippingAgentHelper.StringOrNull(sfOrder.BillAddress.Address2);
			address.Phone = ShippingAgentHelper.StringOrNull(sfOrder.BillAddress.Phone);
			address.PostalCode = ShippingAgentHelper.StringOrNull(sfOrder.BillAddress.Zip);
			address.Region = ShippingAgentHelper.StringOrNull(sfOrder.BillAddress.State);
			return address;
		}

		private static Customer BuildCustomer(COrder sfOrder)
		{
			Customer customer = new Customer();
			StoreFront.SystemBase.Customer sfCustomer = new StoreFront.SystemBase.Customer();
			new CCustomer().FillCustomer(ref sfCustomer, sfOrder.CustomerID);
			customer.CustomerId = sfOrder.CustomerID.ToString();
			customer.CustomerUrl = BuildCustomerUrl("CustProfileMain.aspx", "");
			customer.Email = sfCustomer.Email;
			customer.FirstName = sfCustomer.FirstName;
			customer.LastName = sfCustomer.LastName;
			customer.MerchantUrl = BuildMerchantUrl("management/Customers.aspx", "");
			customer.UserName = sfCustomer.Email;
			return customer;
		}

		private static string BuildCustomerUrl(string page, string query)
		{
			try
			{
				UriBuilder url = new UriBuilder(StoreFrontConfiguration.SiteURL);
				url.Path += page;
				url.Query = query;
				return url.Uri.ToString();
			}
			catch
			{
				return string.Empty;
			}
		}

		private static string BuildDescription(COrderItem sfItem)
		{
			ArrayList descriptions = new ArrayList();
			foreach (CAttribute attribute in sfItem.Attributes)
			{
				StringBuilder description = new StringBuilder();
				description.Append(string.Concat(attribute.Name, ": "));
				ArrayList details = new ArrayList();
				foreach (CAttributeDetail detail in attribute.AttributeDetails)
				{
					if (attribute.AttributeType == tAttributeType.Normal)
						details.Add(detail.Name);
					else
						details.Add(detail.Customor_Custom_Description);
				}
				description.Append(string.Join(" ", (string[]) details.ToArray(typeof(string))));
				descriptions.Add(description.ToString());
			}
			foreach (CGiftWrap wrap in sfItem.GiftWraps)
			{
				descriptions.Add(string.Format("GIFT WRAP FROM: {0} TO: {1} MESSAGE: {2}", wrap.MessageFrom, wrap.MessageTo, wrap.Message));
			}
			return descriptions.Count > 0 ? string.Join(", ", (string[]) descriptions.ToArray(typeof(string))) : null;
		}

		private static InventoryResult BuildInventoryResult(CReportDetails sfOrderSummary)
		{
			using (COrder sfOrder = new COrder(sfOrderSummary.OrderNumber))
			{
				if (sfOrder.OrderAddresses == null)
					return null;

				ArrayList inventory = new ArrayList();
				foreach (COrderAddress sfOrderAddress in sfOrder.OrderAddresses)
				{
					foreach (COrderItem sfItem in sfOrderAddress.OrderItems)
					{
						if (sfItem.Inventory == null || !sfItem.Inventory.InventoryTracked)
							continue;

						CAttributes attributes = new CAttributes();
						foreach (CAttribute attribute in sfItem.Attributes)
							attributes.Add(attribute);

						ItemCount itemCount = new ItemCount();
						itemCount.ItemId = sfItem.OrderItemID.ToString();
						itemCount.Count = sfItem.Inventory.get_InventoryCount(attributes);
						inventory.Add(itemCount);
					}
				}

				InventoryResult result = new InventoryResult();
				result.Success = true;
				result.Inventory = (ItemCount[]) (inventory.Count > 0 ? inventory.ToArray(typeof(ItemCount)) : null);
				return result;
			}
		}

		private static Supplier BuildManufacturer(IProduct sfItem)
		{
			// StoreFront does not fill in the manufacturer
			using (CProductManagement product = new CProductManagement(Convert.ToInt32(sfItem.ProductID)))
			using (CManufacturer manufacturer = new CManufacturer(product.Manufacturer))
			{
				if (string.Compare(manufacturer.ManufacturerAddress.Company, "no manufacturer", true) == 0)
					return null;

				Supplier supplier = new Supplier();
				supplier.Email = ShippingAgentHelper.StringOrNull(manufacturer.ManufacturerAddress.EMail);
				supplier.Name = manufacturer.ManufacturerAddress.Company;
				supplier.SupplierId = product.Manufacturer.ToString();
				return supplier;
			}
		}

		private static string BuildMerchantUrl(string page, string query)
		{
			try
			{
				UriBuilder url = new UriBuilder(StoreFrontConfiguration.SSLPath);
				url.Path += page;
				url.Query = query;
				return url.Uri.ToString();
			}
			catch
			{
				return string.Empty;
			}
		}

		private static NamedAmount[] BuildOrderCharges(ITotalTax sfOrder)
		{
			NamedAmountList charges = new NamedAmountList();
			if (sfOrder.CODTotal != 0) charges[COD].Amount += sfOrder.CODTotal;
			if (sfOrder.CountryTax != 0) charges[Taxes].Amount += sfOrder.CountryTax;
			if (sfOrder.HandlingTotal != 0) charges[Handling].Amount += sfOrder.HandlingTotal;
			if (sfOrder.LocalTax != 0) charges[Taxes].Amount += sfOrder.LocalTax;
			if (sfOrder.ShippingTotal != 0) charges[Shipping].Amount += sfOrder.ShippingTotal;
			if (sfOrder.StateTax != 0) charges[Taxes].Amount += sfOrder.StateTax;
			return (NamedAmount[]) (charges.Count > 0 ? charges.ToArray(typeof(NamedAmount)) : null);
		}

		private static Item[] BuildOrderItems(COrder sfOrder)
		{
			ArrayList items = new ArrayList();
			foreach (COrderAddress sfOrderAddress in sfOrder.OrderAddresses)
			{
				Item[] shipmentItems = BuildPackageItems(sfOrderAddress.OrderItems, false);
				if (shipmentItems != null)
					items.AddRange(shipmentItems);
				shipmentItems = BuildPackageItems(sfOrderAddress.OrderItems, true);
				if (shipmentItems == null) 
					continue;
				for (int shipmentItemIndex = 0; shipmentItemIndex < shipmentItems.Length; shipmentItemIndex++)
				{
					Item shipmentItem = shipmentItems[shipmentItemIndex];
					Item item = null;
					for (int itemIndex = 0; itemIndex < items.Count; itemIndex++)
					{
						if (((Item)items[itemIndex]).ItemId == shipmentItem.ItemId)
						{
							item = (Item) items[itemIndex];
							break;
						}
					}
					if (item == null)
						items.Add(shipmentItem);
					else
					{
						item.Quantity += shipmentItem.Quantity;
						item.LineTotal += shipmentItem.LineTotal;
					}
				}
			}
			return (Item[]) items.ToArray(typeof(Item));
		}

		private static NamedAmount[] BuildOrderPayments(COrder sfOrder)
		{
			NamedAmountList payments = new NamedAmountList();
			if (sfOrder.GiftCertificateTotal != 0) payments[GiftCertificates].Amount += sfOrder.GiftCertificateTotal;
			if (sfOrder.DiscountTotal != 0) payments[Discounts].Amount += sfOrder.DiscountTotal;
			if (!sfOrder.PaymentsPending)
			{
				NamedAmount payment = new NamedAmount();
				payment.Amount = sfOrder.TotalBilledAmt;
				payment.Description = BuildPaymentDescription(sfOrder);
				payment.Name = "Order Payment";
				payments.Add(payment);
			}
			if (sfOrder.BackOrderTotalAmt > 0 && !sfOrder.BOPaymentsPending)
			{
				NamedAmount payment = new NamedAmount();
				payment.Amount = sfOrder.BackOrderTotalAmt;
				payment.Description = BuildPaymentDescription(sfOrder);
				payment.Name = "Backorder Payment";
				payments.Add(payment);
			}
			return (NamedAmount[]) (payments.Count > 0 ? payments.ToArray(typeof(NamedAmount)) : null);
		}

		private static OrderResult BuildOrderResult(COrderManagmentSummary sfOrderSummary)
		{
			Order order = new Order();
			order.CustomerUrl = BuildCustomerUrl("OrderDetail.aspx", "OrderID=" + sfOrderSummary.uid);
			order.MerchantUrl = BuildMerchantUrl("Management/OrderStatus.aspx", "OrderID=" + sfOrderSummary.OrderNumber);
			order.OrderDate = ShippingAgentHelper.ConvertToDateTime(sfOrderSummary.OrderDate);
			order.OrderId = sfOrderSummary.OrderNumber.ToString();
			order.OrderNumber = sfOrderSummary.OrderNumber.ToString();
			order.OrderStatus = ShippingAgentHelper.StringOrNull(ShippingAgentHelper.RemoveHtmlTags(sfOrderSummary.Status));

			try
			{
				using (COrder sfOrder = new COrder(sfOrderSummary.OrderNumber))
				{
					order.OrderTotal = Convert.ToDecimal(sfOrder.GrandTotal);
					order.BillingAddress = BuildBillingAddress(sfOrder);
					order.Charges = BuildOrderCharges(sfOrder);
					order.Customer = BuildCustomer(sfOrder);
					order.Items = BuildOrderItems(sfOrder);
					order.Payments = BuildOrderPayments(sfOrder);
					order.PaymentStatus = BuildPaymentStatus(sfOrder);
					order.Packages = BuildPackages(sfOrder);
					order.ShippingStatus = BuildShippingStatus(sfOrder);
					
					OrderResult result = new OrderResult();
					result.Success = true;
					result.Order = order;
					
					return result;
				}
			}
			catch (Exception ex)
			{
				OrderResult result = new OrderResult();
				result.Success = false;
				result.Message = ex.ToString();
				result.Order = order;
				return result;
			}
		}

		private static OrderResult BuildOrderResult(long sfOrderNumber)
		{
			sfSearchContainer search = new sfSearchContainer();
			search.OrderNumber = sfOrderNumber;
			using (CManagement management = new CManagement(search))
			{
				ArrayList summaries = management.OrderSummary;
				if (summaries.Count == 0)
					throw new ApplicationException("Invalid order ID.");

				return BuildOrderResult((COrderManagmentSummary)summaries[0]);
			}
		}

		private static NamedAmount[] BuildPackageCharges(COrderAddress sfOrderAddress, bool sfBackorder)
		{
			NamedAmountList charges = new NamedAmountList();
			if (sfBackorder)
			{
				if (sfOrderAddress.BackOrderCountryTaxTotal != 0) charges[Taxes].Amount += sfOrderAddress.BackOrderCountryTaxTotal;
				if (sfOrderAddress.BackOrderHandlingTotal != 0) charges[Handling].Amount += sfOrderAddress.BackOrderHandlingTotal;
				if (sfOrderAddress.BackOrderLocalTaxTotal != 0) charges[Taxes].Amount += sfOrderAddress.BackOrderLocalTaxTotal;
				if (sfOrderAddress.BackOrderStateTaxTotal != 0) charges[Taxes].Amount += sfOrderAddress.BackOrderStateTaxTotal;
			}
			else
			{
				if (sfOrderAddress.NonBackOrderCountryTaxTotal != 0) charges[Taxes].Amount += sfOrderAddress.NonBackOrderCountryTaxTotal;
				if (sfOrderAddress.NonBackOrderHandlingTotal != 0) charges[Handling].Amount += sfOrderAddress.NonBackOrderHandlingTotal;
				if (sfOrderAddress.NonBackOrderLocalTaxTotal != 0) charges[Taxes].Amount += sfOrderAddress.NonBackOrderLocalTaxTotal;
				if (sfOrderAddress.NonBackOrderStateTaxTotal != 0) charges[Taxes].Amount += sfOrderAddress.NonBackOrderStateTaxTotal;
				if (sfOrderAddress.ShippingTotal != 0) charges[Shipping].Amount += sfOrderAddress.ShippingTotal;
				if (sfOrderAddress.DiscountTotal != 0) charges[Discounts].Amount += sfOrderAddress.DiscountTotal;
				// Move gift wrap charges from line item total to shipment charges
				foreach (COrderItem item in sfOrderAddress.OrderItems)
				{
					if (item.GiftWrapPrice != 0) charges[Giftwraps].Amount += item.GiftWrapPrice;
				}
			}
			return (NamedAmount[]) (charges.Count > 0 ? charges.ToArray(typeof(NamedAmount)) : null);
		}

		private static Item[] BuildPackageItems(ArrayList sfItems, bool sfBackorder)
		{
			ArrayList items = new ArrayList();
			foreach (COrderItem sfItem in sfItems)
			{
				long backorderQuantity = sfItem.SavedBOQty;
				long orderQuantity = sfItem.Quantity - backorderQuantity;

				if (sfBackorder && backorderQuantity <= 0) continue;
				if (!sfBackorder && orderQuantity <= 0) continue;

				Item item = new Item();
				item.Code = sfItem.ProductCode;
				item.Description = BuildDescription(sfItem);
				if (sfItem.Inventory != null && sfItem.Inventory.InventoryTracked)
				{
					CAttributes attributes = new CAttributes();
					foreach (CAttribute attribute in sfItem.Attributes)
						attributes.Add(attribute);
					item.Inventory = sfItem.Inventory.get_InventoryCount(attributes);
				}
				item.ItemId = sfItem.OrderItemID.ToString();
				item.LineTotal = sfBackorder ? sfItem.BackOrderTotal : sfItem.ItemTotal - sfItem.BackOrderTotal - sfItem.GiftWrapPrice;
				item.Manufacturer = BuildManufacturer(sfItem);
				item.Name = sfItem.Name;
				item.Price = sfItem.ItemPrice;
				item.Quantity = sfBackorder ? backorderQuantity : orderQuantity;
				item.Shippable = sfItem.IsShipable;
				item.Vendor = BuildVendor(sfItem);
				item.Weight = Convert.ToDouble(sfItem.Weight);
				items.Add(item);
			}
			return (Item[]) items.ToArray(typeof(Item));
		}

		private static PackageResult BuildPackageResult(COrder sfOrder, COrderAddress sfOrderAddress, bool sfBackorder)
		{
			if (sfBackorder && !sfOrderAddress.HasBackOrderShippableProducts)
			{
				PackageResult result = new PackageResult();
				result.Success = true;
				return result;
			}
			if (!sfBackorder && !sfOrderAddress.HasNonBackOrderShippableProducts)
			{
				PackageResult result = new PackageResult();
				result.Success = true;
				return result;
			}

			Package package = new Package();
			package.CustomerUrl = BuildCustomerUrl("OrderTracking.aspx", "OrderID=" + sfOrder.UID);
			package.MerchantUrl = BuildMerchantUrl("management/TrackShipment.aspx",
				string.Format("OrderID={0}&OrderAddressID={1}", sfOrder.UID, sfOrderAddress.Address.ID));
			package.OrderId = sfOrder.OrderNumber.ToString();
			package.PackageId = (sfBackorder ? "b" : "a") + sfOrderAddress.Address.ID;

			try
			{
				package.Charges = BuildPackageCharges(sfOrderAddress, sfBackorder);
				package.Destination = BuildShippingAddress(sfOrderAddress.Address, sfOrderAddress.Residential);
				package.Instructions = ShippingAgentHelper.StringOrNull(sfOrderAddress.Address.Instructions);
				package.Items = BuildPackageItems(sfOrderAddress.OrderItems, sfBackorder);
				package.RateOptionName = sfOrderAddress.Address.ShipMethod;
				package.RateProviderName = BuildRateProviderName(sfOrderAddress.Address.ShipCarrierCode);
				package.ShipDate = BuildShipDate(sfOrder, sfOrderAddress, sfBackorder);
				package.ShipDateSpecified = package.ShipDate > DateTime.MinValue;
				package.Shipped = BuildShipped(sfOrder, sfOrderAddress, sfBackorder);
				package.TrackingProviderId = ShippingAgentHelper.StringOrNull(sfOrderAddress.Address.ShipCarrierCode);
				package.TrackingProviderName = package.RateProviderName;
				package.Warehouse = BuildWarehouse();
				package.Weight = BuildPackageWeight(sfOrderAddress.OrderItems, sfBackorder);

				PackageResult result = new PackageResult();
				result.Success = true;
				result.Package = package;
				return result;
			}
			catch (Exception ex)
			{
				PackageResult result = new PackageResult();
				result.Success = false;
				result.Message = ex.ToString();
				result.Package = package;
				return result;
			}
		}

		private static PackageResult[] BuildPackages(COrder sfOrder)
		{
			ArrayList packages = new ArrayList();
			foreach (COrderAddress sfOrderAddress in sfOrder.OrderAddresses)
			{
				if (sfOrderAddress.HasNonBackOrderShippableProducts)
					packages.Add(BuildPackageResult(sfOrder, sfOrderAddress, false));
				if (sfOrderAddress.HasBackOrderShippableProducts)
					packages.Add(BuildPackageResult(sfOrder, sfOrderAddress, true));
			}
			return (PackageResult[]) (packages.Count > 0 ? packages.ToArray(typeof(PackageResult)) : null);
		}

		private static double BuildPackageWeight(ArrayList sfItems, bool sfBackorder)
		{
			decimal weight = 0;
			foreach (COrderItem sfItem in sfItems)
			{
				long backorderQuantity = sfItem.SavedBOQty;
				long orderQuantity = sfItem.Quantity - backorderQuantity;

				if (sfBackorder && backorderQuantity <= 0) continue;
				if (!sfBackorder && orderQuantity <= 0) continue;

				decimal itemWeight = sfItem.Weight + sfItem.AttributesWeight;
				weight += (sfBackorder ? backorderQuantity : orderQuantity) * itemWeight;
			}
			return Convert.ToDouble(weight);
		}

		private static string BuildPaymentDescription(COrder sfOrder)
		{
			string methodName = StoreFrontConfiguration.PaymentMethodAccess.GetPaymentMethodName(sfOrder.PaymentMethod);
			if (string.Compare(methodName, "echeck", true) == 0)
				return methodName;
			if (string.Compare(methodName, "credit card", true) == 0)
				return string.Concat(sfOrder.OrderPayment.CardType, " -", GetCreditCardNumber(sfOrder.OrderPayment));
			if (string.Compare(methodName, "po", true) == 0)
				return string.Concat("Purchase Order #", sfOrder.OrderPayment.PONumber);
			if (string.Compare(methodName, "phonefax", true) == 0)
				return "Phone/Fax";
			if (string.Compare(methodName, "cod", true) == 0)
				return "COD";
			return string.Empty;
		}

		private static string BuildShippingStatus(COrder sfOrder)
		{

			int totalShipments = 0;
			foreach (COrderAddress address in sfOrder.OrderAddresses)
			{
				if (address.HasNonBackOrderShippableProducts)
					totalShipments++;
				if (address.HasBackOrderShippableProducts)
					totalShipments++;
			}
			if (totalShipments == 0)
				return "Non Shipping";

			int pendingShipments = 0;
			using (CManagement management = new CManagement(sfOrder.UID))
			{
				foreach (COrderShipmentStatus status in management.get_OrderShipStatus(sfOrder.UID))
				{
					if (status.HasShipmentPending)
						pendingShipments++;
					if (status.HasBOShipmentPending)
						pendingShipments++;
				}
			}
			if (pendingShipments == 0)
				return "Shipped";

			return pendingShipments == totalShipments ? "Unshipped" : "Partially Shipped";
		}

		private static string GetCreditCardNumber(COrderPayment sfOrderPayment)
		{
			if (sfOrderPayment.LastFourDigits != null && sfOrderPayment.LastFourDigits != string.Empty)
				return sfOrderPayment.LastFourDigits;
			if (sfOrderPayment.CreditCardNumber != null && sfOrderPayment.CreditCardNumber != string.Empty)
			{
				string number = GetDecryptedValue(sfOrderPayment.CreditCardNumber);
				return number.Length > 4 ? number.Substring(number.Length - 4) : number;
			}
			return string.Empty;
		}

		private static string GetDecryptedValue(string value)
		{
			if (StoreFrontConfiguration.ConvertedFrom3DES)
				return string.Empty;
			string[] values = value.Split(' ');
			if (values.Length < 17)
				return string.Empty;

			try
			{
				byte[] key = new byte[8];
				byte[] iv = new byte[8];
				for (int index = 0; index < 8; index++)
				{
					key[index] = Convert.ToByte(values[index]);
					iv[index] = Convert.ToByte(values[index + 8]);
				}
				byte[] encrypted = new byte[values.Length - 16];
				for (int index = 0; index <= encrypted.GetUpperBound(0); index++)
					encrypted[index] = Convert.ToByte(values[index + 16]);

				using (CStoreFrontCrypto2 crypto = new CStoreFrontCrypto2(encrypted))
				{
					crypto.Type = CryptoType.Decrypt;
					return crypto.GetData(key, iv);
				}
			}
			catch
			{
				return string.Empty;
			}
		}

		private static string BuildPaymentStatus(COrder sfOrder)
		{
			if (sfOrder.BackOrderTotalAmt > 0)
			{
				if (sfOrder.PaymentsPending && sfOrder.BOPaymentsPending)
					return "Unpaid";
				if (!sfOrder.PaymentsPending && !sfOrder.BOPaymentsPending)
					return "Paid";
				return "Partially Paid";
			}
			return sfOrder.PaymentsPending ? "Unpaid" : "Paid";
		}

		private static string BuildRateProviderName(string carrierCode)
		{
			if (carrierCode == null || carrierCode == string.Empty)
				return null;

			using (CShippingManagement management = new CShippingManagement())
			using (DataSet carriers = management.getCarriers())
			{
				foreach (DataRow row in carriers.Tables[0].Rows)
				{
					if (string.Compare(ShippingAgentHelper.ConvertToString(row["Code"]), carrierCode, true) == 0)
					{
						string name = ShippingAgentHelper.ConvertToString(row["Name"]);
						if (string.Compare(name, "<Please Select a Carrier>", true) == 0)
							return null;
						else
							return ShippingAgentHelper.StringOrNull(name);
					}
				}
			}
			return carrierCode;
		}

		private static DateTime BuildShipDate(COrder sfOrder, COrderAddress sfOrderAddress, bool sfBackorder)
		{
			using (CManagement management = new CManagement(sfOrder.UID))
			{
				foreach (COrderShipmentStatus status in management.get_OrderShipStatus(sfOrder.UID))
				{
					if (status.AddressID != sfOrderAddress.Address.ID)
						continue;
					string sentdate = management.get_Tracking(sfOrderAddress.Address.ID, sfBackorder ? 1 : 0).SentDate;
					if (sentdate == null || sentdate == string.Empty)
						return DateTime.MinValue;
					DateTime outdate;
					try
					{
						outdate = DateTime.Parse(sentdate);
						return outdate;
					}
					catch
					{
						return DateTime.MinValue;
					}
				}
			}
			return DateTime.MinValue;
		}

		private static bool BuildShipped(COrder sfOrder, COrderAddress sfOrderAddress, bool sfBackorder)
		{
			using (CManagement management = new CManagement(sfOrder.UID))
			{
				foreach (COrderShipmentStatus status in management.get_OrderShipStatus(sfOrder.UID))
				{
					if (status.AddressID != sfOrderAddress.Address.ID)
						continue;
					if (sfBackorder && !status.HasBOShipmentPending)
						return true;
					if (!sfBackorder && !status.HasShipmentPending)
						return true;
				}
			}
			return false;
		}

		private static ShippingAddress BuildShippingAddress(StoreFront.SystemBase.Address sfAddress, bool residential)
		{
			ShippingAddress shippingAddress = new ShippingAddress();
			shippingAddress.AddressId = sfAddress.ID.ToString();
			shippingAddress.City = ShippingAgentHelper.StringOrNull(sfAddress.City);
			shippingAddress.Company = ShippingAgentHelper.StringOrNull(sfAddress.Company);
			shippingAddress.Country = ShippingAgentHelper.StringOrNull(sfAddress.Country);
			shippingAddress.Email = ShippingAgentHelper.StringOrNull(sfAddress.EMail);
			shippingAddress.Fax = ShippingAgentHelper.StringOrNull(sfAddress.Fax);
			shippingAddress.FirstName = ShippingAgentHelper.StringOrNull(sfAddress.FirstName);
			shippingAddress.LastName = ShippingAgentHelper.StringOrNull(sfAddress.LastName);
			shippingAddress.Line1 = ShippingAgentHelper.StringOrNull(sfAddress.Address1);
			shippingAddress.Line2 = ShippingAgentHelper.StringOrNull(sfAddress.Address2);
			shippingAddress.Phone = ShippingAgentHelper.StringOrNull(sfAddress.Phone);
			shippingAddress.PostalCode = ShippingAgentHelper.StringOrNull(sfAddress.Zip);
			shippingAddress.Region = ShippingAgentHelper.StringOrNull(sfAddress.State);
			shippingAddress.Residential = residential;
			return shippingAddress;
		}

		private static OrderResult BuildUpdateResult(Update update, COrder sfOrder, COrderAddress sfOrderAddress, bool sfBackorder)
		{
			try
			{
				ArrayList notes = new ArrayList();

				if (update.Destination != null)
				{
					if (!AddressesAreEqual(sfOrderAddress, update.Destination))
					{
						using (COrderAddressAccess access = new COrderAddressAccess())
						using (DataSet addresses = access.GetOrderAddress(sfOrder.UID))
						{
							if (addresses.Tables.Count == 0)
								throw new ApplicationException("Invalid order ID.");

							DataRow[] sfAddress = addresses.Tables[0].Select("UID=" + sfOrderAddress.Address.ID);
							if (sfAddress.Length == 0)
								throw new ApplicationException("Invalid package ID.");

							sfAddress[0]["Address1"] = update.Destination.Line1;
							sfAddress[0]["Address2"] = update.Destination.Line2;
							sfAddress[0]["City"] = update.Destination.City;
							sfAddress[0]["Company"] = update.Destination.Company;
							sfAddress[0]["Country"] = update.Destination.Country;
							sfAddress[0]["Fax"] = update.Destination.Fax;
							sfAddress[0]["FirstName"] = update.Destination.FirstName;
							sfAddress[0]["LastName"] = update.Destination.LastName;
							sfAddress[0]["Phone"] = update.Destination.Phone;
							sfAddress[0]["State"] = update.Destination.Region;
							sfAddress[0]["Zip"] = update.Destination.PostalCode;
							sfAddress[0]["Email"] = update.Destination.Email;
							access.Update(addresses, access.TableName);
						}
						notes.Add("Changed shipping address.");
					}
				}

				if (update.TrackingNumbers != null)
				{
					using (COrderTrackingAccess access = new COrderTrackingAccess())
					using (DataSet tracking = access.GetTracking(sfOrderAddress.Address.ID))
					{
						if (tracking.Tables.Count == 0)
							throw new ApplicationException("Invalid package ID.");

						DataRow[] sfTrack = tracking.Tables[0].Select("(VendorName='' OR VendorName IS NULL) AND BackOrderFlag=" + (sfBackorder ? 1 : 0));
						if (sfTrack.Length == 0)
						{
							sfTrack = new DataRow[] { tracking.Tables[0].NewRow() };
							sfTrack[0]["BackOrderFlag"] = sfBackorder;
							sfTrack[0]["ShipToAddressID"] = sfOrderAddress.Address.ID;
							sfTrack[0]["VendorName"] = string.Empty;
							tracking.Tables[0].Rows.Add(sfTrack[0]);
						}
						string trackingNumbers = string.Join(",", update.TrackingNumbers);
						if (sfTrack[0].IsNull("TrackingNumber") || sfTrack[0]["TrackingNumber"].ToString() != trackingNumbers)
						{
							sfTrack[0]["TrackingNumber"] = trackingNumbers;
							access.Update(tracking, access.TableName);
							notes.Add(string.Format("Changed shipment tracking number to {0}.", trackingNumbers));
						}
					}
				}

				if (update.TrackingProviderId != null)
				{
					if (string.Compare(sfOrderAddress.Address.ShipCarrierCode, update.TrackingProviderId, true) != 0)
					{
						using (COrderAddressAccess access = new COrderAddressAccess())
						using (DataSet addresses = access.GetOrderAddress(sfOrder.UID))
						{
							if (addresses.Tables.Count == 0)
								throw new ApplicationException("Invalid order ID.");

							DataRow[] sfAddress = addresses.Tables[0].Select("UID=" + sfOrderAddress.Address.ID);
							if (sfAddress.Length == 0)
								throw new ApplicationException("Invalid package ID.");

							sfAddress[0]["ShipCarrierCode"] = update.TrackingProviderId;
							access.Update(addresses, access.TableName);
						}
						notes.Add(string.Format("Changed shipment tracking provider code to {0}.", update.TrackingProviderId));
					}
				}

				if (update.ShipDateSpecified)
				{
					DateTime shipdate = BuildShipDate(sfOrder, sfOrderAddress, sfBackorder);
					if (shipdate == DateTime.MinValue || update.ShipDate != shipdate)
					{
						using (COrderAddressAccess access = new COrderAddressAccess())
						using (DataSet addresses = access.GetOrderAddress(sfOrder.UID))
						{
							if (addresses.Tables.Count == 0)
								throw new ApplicationException("Invalid order ID.");

							DataRow[] sfAddress = addresses.Tables[0].Select("UID=" + sfOrderAddress.Address.ID);
							if (sfAddress.Length == 0)
								throw new ApplicationException("Invalid package ID.");

							if (sfBackorder)
								sfAddress[0]["BOPending"] = 0;
							else
								sfAddress[0]["Pending"] = 0;
							access.Update(addresses, access.TableName);
						}

						using (COrderTrackingAccess access = new COrderTrackingAccess())
						using (DataSet tracking = access.GetTracking(sfOrderAddress.Address.ID))
						{
							if (tracking.Tables.Count == 0)
								throw new ApplicationException("Invalid package ID.");

							DataRow[] sfTrack = tracking.Tables[0].Select("(VendorName='' OR VendorName IS NULL) AND BackOrderFlag=" + (sfBackorder ? 1 : 0));
							if (sfTrack.Length == 0)
							{
								sfTrack = new DataRow[] { tracking.Tables[0].NewRow() };
								sfTrack[0]["BackOrderFlag"] = sfBackorder;
								sfTrack[0]["ShipToAddressID"] = sfOrderAddress.Address.ID;
								sfTrack[0]["VendorName"] = string.Empty;
								tracking.Tables[0].Rows.Add(sfTrack[0]);
							}
							sfTrack[0]["SentDate"] = update.ShipDate.ToString("M/d/yyyy");
							access.Update(tracking, access.TableName);
						}
						notes.Add(string.Format("Changed shipping date to {0}.", update.ShipDate.ToString("M/d/yyyy")));
					}
				}

				if (notes.Count > 0)
				{
					OrderResult result = BuildOrderResult(sfOrder.OrderNumber);
					result.Message = string.Join(" ", (string[]) notes.ToArray(typeof(string)));
					return result;
				}

				OrderResult nochange = new OrderResult();
				nochange.Success = true;
				nochange.Message = "No change.";
				return nochange;
			}
			catch (Exception ex)
			{
				OrderResult result = new OrderResult();
				result.Success = false;
				result.Message = ex.ToString();
				return result;
			}
		}

		private static Supplier BuildVendor(IProduct sfItem)
		{
			using (CProductManagement product = new CProductManagement(Convert.ToInt32(sfItem.ProductID)))
			using (CVendor vendor = new CVendor(product.Vendor))
			{
				if (string.Compare(vendor.VendorAddress.Company, "no vendor", true) == 0)
					return null;

				Supplier supplier = new Supplier();
				supplier.Email = ShippingAgentHelper.StringOrNull(vendor.VendorAddress.EMail);
				supplier.Name = vendor.VendorAddress.Company;
				supplier.SupplierId = product.Vendor.ToString();
				return supplier;
			}
		}

		private static Address BuildWarehouse()
		{
			using (CShippingManagement management = new CShippingManagement())
			{
				Address address = new Address();
				address.AddressId = "Origin";
				address.City = ShippingAgentHelper.StringOrNull(management.AdminShipping.OriginCity);
				address.Company = ShippingAgentHelper.StringOrNull(management.AdminShipping.StoreName);
				address.Country = ShippingAgentHelper.StringOrNull(management.AdminShipping.OriginCountry);
				address.Email = ShippingAgentHelper.StringOrNull(management.AdminShipping.PrimaryEmail);
				address.PostalCode = ShippingAgentHelper.StringOrNull(management.AdminShipping.OriginZip);
				address.Region = ShippingAgentHelper.StringOrNull(management.AdminShipping.OriginState);
				return address;
			}
		}
	}
}